use clap::{Args, Parser};
use std::{
    path::{Path, PathBuf},
    process::Command,
};

#[derive(Debug)]
struct Error;

type Result<T> = std::result::Result<T, Error>;

#[derive(Parser)]
enum Cli {
    /// Install ezpass
    Install(OptInstall),
    /// Uninstall ezpass
    Uninstall,
}

#[derive(Args)]
struct OptInstall {
    /// Prefix of install directory. Default be "~/.local"
    #[clap(short, long)]
    prefix: Option<PathBuf>,
}

impl From<std::io::Error> for Error {
    fn from(err: std::io::Error) -> Self {
        eprintln!("IO Error: {}", err);
        Self
    }
}

fn run(cmd: &mut Command) -> Result<()> {
    eprintln!("RUN: {:?}", cmd);

    let status = cmd.status()?;

    if !status.success() {
        eprintln!("Command failed");
        return Err(Error);
    }

    Ok(())
}

fn main() {
    let cli = Cli::parse();

    let result = match cli {
        Cli::Install(opt) => install(&opt),
        Cli::Uninstall => uninstall(),
    };

    if result.is_err() {
        std::process::exit(1);
    }
}

fn uninstall() -> Result<()> {
    Ok(())
}

fn install(opt: &OptInstall) -> Result<()> {
    let prefix = match opt.prefix.as_ref() {
        Some(x) => x.canonicalize()?,
        None => dirs::home_dir()
            .ok_or_else(|| {
                eprintln!("Can't find home directory");
                Error
            })?
            .join(".local")
            .canonicalize()?,
    };

    let target_dir = Path::new("target");

    run(Command::new(env!("CARGO"))
        .arg("build")
        .arg("--release")
        .arg(format!(
            "--target-dir={target_dir}/",
            target_dir = target_dir.display()
        )))?;

    pkgutils::SourceFile::new(target_dir.join("release").join("ezpass"))
        .install(prefix.join("bin/ezpass"))?
        .chmod(0o755)?;

    Ok(())
}

mod pkgutils {
    use super::Result;
    use std::{
        os::unix::fs::PermissionsExt,
        path::{Path, PathBuf},
    };

    pub struct TargetFile {
        path: PathBuf,
    }

    impl TargetFile {
        pub fn chmod(&mut self, mode: u32) -> Result<()> {
            println!("Change mode of {} to {:o}", self.path.display(), mode);
            std::fs::set_permissions(&self.path, PermissionsExt::from_mode(mode))?;
            Ok(())
        }
    }

    pub struct SourceFile(PathBuf);

    impl SourceFile {
        pub fn install(&self, to: PathBuf) -> Result<TargetFile> {
            mkdir_basename(&to);

            println!("Copy {} to {}", self.0.display(), to.display());
            std::fs::copy(&self.0, &to)?;

            Ok(TargetFile { path: to })
        }

        pub(crate) fn new(path: PathBuf) -> Self {
            Self(path)
        }
    }

    fn mkdir_basename(path: &Path) {
        if let Some(parent) = path.parent() {
            println!("Create directory: {}", parent.display());
            let _ = std::fs::create_dir_all(parent);
        }
    }

    // pub struct SourceString(String);

    // impl SourceString {
    //     pub fn install(&self, to: PathBuf) -> Result<TargetFile> {
    //         mkdir_basename(&to);

    //         println!("Generate {}", to.display());
    //         std::fs::write(&to, &self.0)?;

    //         Ok(TargetFile { path: to })
    //     }

    //     pub fn new(content: String) -> Self {
    //         Self(content)
    //     }
    // }
}
